package system

import (
	"log"
	"sync"
	"time"

	"github.com/asynkron/protoactor-go/actor"
	"github.com/asynkron/protoactor-go/cluster"
)

// ClusterManager is an actor that manages cluster membership
type ClusterManager struct {
	// nodes is a map of node IDs to node info
	nodes map[string]*NodeInfo
	// statusListeners are the actors listening for node status changes
	statusListeners []*actor.PID
	// mu is a mutex for protecting the nodes map
	mu sync.RWMutex
	// logger is the cluster manager logger
	logger *log.Logger
	// cluster is the ProtoActor cluster
	cluster *cluster.Cluster
}

// NodeInfo contains information about a cluster node
type NodeInfo struct {
	// ID is the node ID
	ID string
	// Address is the node address
	Address string
	// Status is the node status
	Status NodeStatus
	// LastSeen is when the node was last seen
	LastSeen time.Time
	// Metadata contains additional node metadata
	Metadata map[string]string
}

// NodeStatus represents the status of a node
type NodeStatus int

const (
	// NodeStatusUnknown indicates the node status is unknown
	NodeStatusUnknown NodeStatus = iota
	// NodeStatusUp indicates the node is up
	NodeStatusUp
	// NodeStatusDown indicates the node is down
	NodeStatusDown
	// NodeStatusLeaving indicates the node is leaving the cluster
	NodeStatusLeaving
)

// String returns the string representation of the node status
func (s NodeStatus) String() string {
	switch s {
	case NodeStatusUnknown:
		return "unknown"
	case NodeStatusUp:
		return "up"
	case NodeStatusDown:
		return "down"
	case NodeStatusLeaving:
		return "leaving"
	default:
		return "unknown"
	}
}

// NodeStatusChanged is an event for node status changes
type NodeStatusChanged struct {
	// NodeID is the ID of the node
	NodeID string
	// Status is the new status
	Status NodeStatus
	// Address is the node address
	Address string
	// Timestamp is when the status changed
	Timestamp time.Time
}

// NewClusterManager creates a new cluster manager
func NewClusterManager() actor.Actor {
	return &ClusterManager{
		nodes:           make(map[string]*NodeInfo),
		statusListeners: make([]*actor.PID, 0),
		logger:          log.New(log.Writer(), "[ClusterManager] ", log.LstdFlags),
	}
}

// Receive handles messages for the cluster manager
func (c *ClusterManager) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		c.handleStarted(context)
	case *actor.Stopping:
		c.handleStopping(context)
	case *GetClusterNodesCommand:
		c.handleGetClusterNodes(context)
	case *SubscribeNodeStatusCommand:
		c.handleSubscribeNodeStatus(context, msg)
	case *UnsubscribeNodeStatusCommand:
		c.handleUnsubscribeNodeStatus(context, msg)
	case *CheckNodeStatusCommand:
		c.handleCheckNodeStatus(context, msg)
	default:
		c.logger.Printf("Received unknown message: %T", msg)
	}
}

// handleStarted handles the Started message
func (c *ClusterManager) handleStarted(context actor.Context) {
	c.logger.Println("Cluster manager started")

	// Start periodic node status check
	context.Send(context.Self(), &CheckNodeStatusCommand{})
}

// handleStopping handles the Stopping message
func (c *ClusterManager) handleStopping(context actor.Context) {
	c.logger.Println("Cluster manager stopping")
}

// handleGetClusterNodes handles the GetClusterNodesCommand message
func (c *ClusterManager) handleGetClusterNodes(context actor.Context) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	// Create a copy of the nodes map
	nodes := make(map[string]*NodeInfo)
	for id, node := range c.nodes {
		nodeCopy := *node
		nodes[id] = &nodeCopy
	}

	// Respond with the nodes
	context.Respond(&GetClusterNodesResponse{
		Nodes: nodes,
	})
}

// handleSubscribeNodeStatus handles the SubscribeNodeStatusCommand message
func (c *ClusterManager) handleSubscribeNodeStatus(context actor.Context, msg *SubscribeNodeStatusCommand) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Add the subscriber
	c.statusListeners = append(c.statusListeners, context.Sender())

	// Respond with success
	context.Respond(&SubscribeNodeStatusResponse{
		Success: true,
	})

	c.logger.Printf("Node status subscriber added: %s", context.Sender().String())
}

// handleUnsubscribeNodeStatus handles the UnsubscribeNodeStatusCommand message
func (c *ClusterManager) handleUnsubscribeNodeStatus(context actor.Context, msg *UnsubscribeNodeStatusCommand) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Find and remove the subscriber
	for i, pid := range c.statusListeners {
		if pid.String() == context.Sender().String() {
			c.statusListeners = append(c.statusListeners[:i], c.statusListeners[i+1:]...)
			break
		}
	}

	// Respond with success
	context.Respond(&UnsubscribeNodeStatusResponse{
		Success: true,
	})

	c.logger.Printf("Node status subscriber removed: %s", context.Sender().String())
}

// handleCheckNodeStatus handles the CheckNodeStatusCommand message
func (c *ClusterManager) handleCheckNodeStatus(context actor.Context, msg *CheckNodeStatusCommand) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Check for nodes that haven't been seen recently
	now := time.Now()
	for id, node := range c.nodes {
		if node.Status == NodeStatusUp && now.Sub(node.LastSeen) > 30*time.Second {
			c.logger.Printf("Node %s hasn't been seen for 30 seconds, marking as down", id)
			node.Status = NodeStatusDown
			c.notifyStatusChange(context, id, NodeStatusDown)
		}
	}

	// Schedule next check
	context.Send(context.Self(), &CheckNodeStatusCommand{})
}

// notifyStatusChange notifies listeners of a node status change
func (c *ClusterManager) notifyStatusChange(context actor.Context, nodeID string, status NodeStatus) {
	// Get node address
	address := ""
	if node, ok := c.nodes[nodeID]; ok {
		address = node.Address
	}

	// Create the event
	event := &NodeStatusChanged{
		NodeID:    nodeID,
		Status:    status,
		Address:   address,
		Timestamp: time.Now(),
	}

	// Notify all listeners
	for _, pid := range c.statusListeners {
		context.Send(pid, event)
	}
}

// GetClusterNodesCommand is a command to get all cluster nodes
type GetClusterNodesCommand struct{}

// GetClusterNodesResponse is the response to a GetClusterNodesCommand
type GetClusterNodesResponse struct {
	// Nodes is a map of node IDs to node info
	Nodes map[string]*NodeInfo
}

// SubscribeNodeStatusCommand is a command to subscribe to node status changes
type SubscribeNodeStatusCommand struct{}

// SubscribeNodeStatusResponse is the response to a SubscribeNodeStatusCommand
type SubscribeNodeStatusResponse struct {
	// Success indicates if the operation was successful
	Success bool
	// Error is the error message if the operation failed
	Error string
}

// UnsubscribeNodeStatusCommand is a command to unsubscribe from node status changes
type UnsubscribeNodeStatusCommand struct{}

// UnsubscribeNodeStatusResponse is the response to an UnsubscribeNodeStatusCommand
type UnsubscribeNodeStatusResponse struct {
	// Success indicates if the operation was successful
	Success bool
	// Error is the error message if the operation failed
	Error string
}

// CheckNodeStatusCommand is a command to check node status
type CheckNodeStatusCommand struct{}
